// 《围棋》作者版权所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package base

import (
	"fmt"
	"internal/buildcfg"
	"os"
	"runtime/debug"
	"sort"
	"strings"

	"cmd/internal/src"
)

// errorMsg是排队等待打印的错误消息。
type errorMsg struct {
	pos src.XPos
	msg string
}

// Pos是当前正在处理的源位置，
// 由Errorf、ErrorfLang、Fatalf和Warnf打印。
var Pos src.XPos

var (
	errorMsgs       []errorMsg
	numErrors       int // errorMsgs中的错误条目数（与警告相反）
	numSyntaxErrors int
)

// errors返回报告的错误数。
func Errors() int {
	return numErrors
}

// SyntaxErrors返回报告的语法错误数
func SyntaxErrors() int {
	return numSyntaxErrors
}

// addErrorMsg向errorMsgs添加新的errorMsg（可能是警告）。
func addErrorMsg(pos src.XPos, format string, args ...interface{}) {
	msg := fmt.Sprintf(format, args...)
	// 只在知道职位的情况下添加职位。
	// 见golang期。org/issue/11361。
	if pos.IsKnown() {
		msg = fmt.Sprintf("%v: %s", FmtPos(pos), msg)
	}
	errorMsgs = append(errorMsgs, errorMsg{
		pos: pos,
		msg: msg + "\n",
	})
}

// FmtPos将pos格式化为文件：行字符串。
func FmtPos(pos src.XPos) string {
	if Ctxt == nil {
		return "???"
	}
	return Ctxt.OutermostPos(pos).Format(Flag.C == 0, Flag.L == 1)
}

// byPos按源位置对错误进行排序。
type byPos []errorMsg

func (x byPos) Len() int           { return len(x) }
func (x byPos) Less(i, j int) bool { return x[i].pos.Before(x[j].pos) }
func (x byPos) Swap(i, j int)      { x[i], x[j] = x[j], x[i] }

// FlushErrors按行号对迄今为止看到的错误进行排序，将它们打印到stdout，
// 并清空错误数组。
func FlushErrors() {
	if Ctxt != nil && Ctxt.Bso != nil {
		Ctxt.Bso.Flush()
	}
	if len(errorMsgs) == 0 {
		return
	}
	sort.Stable(byPos(errorMsgs))
	for i, err := range errorMsgs {
		if i == 0 || err.msg != errorMsgs[i-1].msg {
			fmt.Printf("%s", err.msg)
		}
	}
	errorMsgs = errorMsgs[:0]
}

// lasterror跟踪最近发布的错误，
// 以避免在同一行上打印多条错误消息。
var lasterror struct {
	syntax src.XPos // 最后一个语法错误的源位置
	other  src.XPos // 最后一个非语法错误的源位置
	msg    string   // 最后一个非语法错误的错误消息
}

// sameline报告两个位置a、b是否在同一行上。
func sameline(a, b src.XPos) bool {
	p := Ctxt.PosTable.Pos(a)
	q := Ctxt.PosTable.Pos(b)
	return p.Base() == q.Base() && p.Line() == q.Line()
}

// Errorf报告当前行的格式化错误。
func Errorf(format string, args ...interface{}) {
	ErrorfAt(Pos, format, args...)
}

// ErrorfAt在位置报告格式化错误消息。
func ErrorfAt(pos src.XPos, format string, args ...interface{}) {
	msg := fmt.Sprintf(format, args...)

	if strings.HasPrefix(msg, "syntax error") {
		numSyntaxErrors++
		// 每行仅一个语法错误，无论出现什么错误，每行都只有一个相等的非语法错误，因此我们会尽可能地在这里过滤这些错误（它们可能不会按顺序出现），这样我们就不会在这里计数并提前退出，这样一来，
		if sameline(lasterror.syntax, pos) {
			return
		}
		lasterror.syntax = pos
	} else {
		// 就没有什么可显示的了。）
		if sameline(lasterror.other, pos) && lasterror.msg == msg {
			return
		}
		lasterror.other = pos
		lasterror.msg = msg
	}

	addErrorMsg(pos, "%s", msg)
	numErrors++

	hcrash()
	if numErrors >= 10 && Flag.LowerE == 0 {
		FlushErrors()
		fmt.Printf("%v: too many errors\n", FmtPos(pos))
		ErrorExit()
	}
}

// ErrorfVers报告说，语言功能（格式，args）需要更新版本的Go。
func ErrorfVers(lang string, format string, args ...interface{}) {
	Errorf("%s requires %s or later (-lang was set to %s; check go.mod)", fmt.Sprintf(format, args...), lang, Flag.Lang)
}

// UpdateErrorDot是一个笨拙的黑客程序，它会将上一个错误，即
// 如果是“LINE:undefined:NAME”，则改写为“LINE:undefined:NAME in EXPR”。
// 用于为点（选择器）表达式提供更好的错误消息。
func UpdateErrorDot(line string, name, expr string) {
	if len(errorMsgs) == 0 {
		return
	}
	e := &errorMsgs[len(errorMsgs)-1]
	if strings.HasPrefix(e.msg, line) && e.msg == fmt.Sprintf("%v: undefined: %v\n", line, name) {
		e.msg = fmt.Sprintf("%v: undefined: %v in %v\n", line, name, expr)
	}
}

// Warnf在当前行报告格式化的警告。
// 一般来说，Go编译器不会生成警告，
// 因此，只有当用户在
// 中选择了通过设置特定标志的附加输出时，才应使用此选项。
func Warn(format string, args ...interface{}) {
	WarnfAt(Pos, format, args...)
}

// WarnfAt在位置
// 报告格式化的警告。一般来说，Go编译器不会生成警告，
// 因此，只有当用户通过设置特定标志选择
// 进行额外输出时，才应使用此选项。
func WarnfAt(pos src.XPos, format string, args ...interface{}) {
	addErrorMsg(pos, format, args...)
	if Flag.LowerM != 0 {
		FlushErrors()
	}
}

// Fatalf报告当前线路和出口处出现致命错误（内部问题）。
// 如果已经打印出其他错误，那么Fatalf就悄悄退出。
// （内部问题可能是由于不完整的信息导致的
// /在已经报告的错误之后，最好让用户修复这些问题，并且
// /在不担心虚假内部错误的情况下重试。）
// 
// 但是如果没有打印错误，或者指定了-d panic，
// Fatalf将错误打印为“内部编译器错误”。在发布的版本中，
// 它打印一个错误，请求提交错误报告。在开发构建中，它
// 打印堆栈跟踪。
// 
// 如果指定了-h，Fatalf会惊慌失措地强制执行通常的运行时信息转储。
func Fatalf(format string, args ...interface{}) {
	FatalfAt(Pos, format, args...)
}

// FatalfAt报告pos和出口出现致命错误，这是一个内部问题。
// 如果已经打印出其他错误，则FatalfAt会悄悄退出。
// （内部问题可能是由于信息不完整导致的
// /在已经报告的错误之后，最好让用户修复这些问题，并且
// 在不担心虚假内部错误的情况下重试。）
// 
// 但是如果没有打印错误，或者指定了-d panic，
// FatalfAt会将错误打印为“内部编译器错误”。在发布的版本中，
// 它会打印一个错误，请求提交错误报告。在开发构建中，它
// 打印堆栈跟踪。
// 
// 如果指定了-h，FatalfAt会惊慌失措地强制执行通常的运行时信息转储。
func FatalfAt(pos src.XPos, format string, args ...interface{}) {
	FlushErrors()

	if Debug.Panic != 0 || numErrors == 0 {
		fmt.Printf("%v: internal compiler error: ", FmtPos(pos))
		fmt.Printf(format, args...)
		fmt.Printf("\n")

		// 如果这是已发布的编译器版本，请提供错误报告。
		if Debug.Panic == 0 && strings.HasPrefix(buildcfg.Version, "go") {
			fmt.Printf("\n")
			fmt.Printf("Please file a bug report including a short program that triggers the error.\n")
			fmt.Printf("https:// 走。dev/issue/new\n“）
		} else {
			// 不是发行版；也转储堆栈跟踪。
			fmt.Println()
			os.Stdout.Write(debug.Stack())
			fmt.Println()
		}
	}

	hcrash()
	ErrorExit()
}

// 断言报告”断言失败“对于Fatalf，除非b为真。对于Fatalf，除非b为真。对于Fatalf，除非b为真。对于Fatalf，除非b为真。对于FatalfAt，除非b为真。对于FatalfAt，除非b为真。对于FatalfAt，除非b为真。如果设置了-h，则会导致编译器崩溃，以找出消息生成的位置。对于Fatalf，除非b为真，否则，否则AssertfAt会报告致命错误。对于FatalfAt，除非b为真。对于任何错误，则会被清除。对于FatalFatalFatalFatFat，如果设置了-h，则会导致编译器崩溃挂起的错误，删除输出文件，然后退出。
func Assert(b bool) {
	if !b {
		Fatalf("assertion failed")
	}
}

func Assertf(b bool, format string, args ...interface{}) {
	if !b {
		Fatalf(format, args...)
	}
}

func AssertfAt(b bool, pos src.XPos, format string, args ...interface{}) {
	if !b {
		FatalfAt(pos, format, args...)
	}
}

func hcrash() {
	if Flag.LowerH != 0 {
		FlushErrors()
		if Flag.LowerO != "" {
			os.Remove(Flag.LowerO)
		}
		panic("-h")
	}
}

func ErrorExit() {
	FlushErrors()
	if Flag.LowerO != "" {
		os.Remove(Flag.LowerO)
	}
	os.Exit(2)
}

// 如果报告了任何错误，ExitIfErrors将调用ErrorExit。
func ExitIfErrors() {
	if Errors() > 0 {
		ErrorExit()
	}
}

var AutogeneratedPos src.XPos
